﻿using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net;
using System.Security.Cryptography;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;

namespace UpYun.Storage
{
    using System.Net.Http;
    using System.Threading.Tasks;
    using UpYun.Storage.Model;
    using static System.Net.WebRequestMethods;

    /// <summary>
    /// 云存储
    /// </summary>
    public class StorageHelper
    {
        /// <summary>
        /// 空间名称
        /// </summary>
        public string BucketName { get; set; }

        /// <summary>
        /// 用户名（ftp）
        /// </summary>
        public string UserName { get; set; }

        /// <summary>
        /// 密码（ftp）
        /// </summary>
        public string Password { get; set; }

        /// <summary>
        /// 存储上传接口
        /// </summary>
        public string ApiDomainStorage { get; set; }

        /// <summary>
        /// 音视频异步处理接口
        /// </summary>
        public string ApiDomainSyncAudioVideo { get; set; }

        /// <summary>
        /// 音视频同步处理接口
        /// </summary>
        public string ApiDomainAudioVideo { get; set; }

        /// <summary>
        /// 又拍云根目录
        /// </summary>
        public string RootDirectory { get; set; }

        /// <summary>
        /// 是否自动创建文件夹
        /// </summary>
        public bool AutoMkdir { get; set; }

        /// <summary>
        /// 文件密钥，用于保护文件，防止文件被直接访问
        /// </summary>
        public string FileSecret { get; set; }

        /// <summary>
        /// 又拍云相应信息临时存储
        /// </summary>
        public Dictionary<string, string> TemporaryInformation { get; set; }

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="bucketName">空间名</param>
        /// <param name="userName">用户名</param>
        /// <param name="password">密码</param>
        /// <param name="apiDomainStorage">存储上传接口</param>
        /// <param name="apiDomainSyncAudioVideo">音视频异步处理接口</param>
        /// <param name="apiDomainAudioVideo">音视频同步处理接口</param>
        /// <param name="autoMkdir">是否自动创建目录</param>
        public StorageHelper(string bucketName, string userName, string password,
            string apiDomainStorage = "https://v0.api.upyun.com", string apiDomainSyncAudioVideo = "https://p0.api.upyun.com",
            string apiDomainAudioVideo = "https://p1.api.upyun.com", bool autoMkdir = true)
        {
            this.BucketName = bucketName;
            this.UserName = userName;
            this.Password = password;
            this.ApiDomainStorage = apiDomainStorage;
            this.ApiDomainSyncAudioVideo = apiDomainSyncAudioVideo;
            this.ApiDomainAudioVideo = apiDomainAudioVideo;
            this.AutoMkdir = autoMkdir;

            this.TemporaryInformation = new Dictionary<string, string>();
            this.RootDirectory = "/";
        }

        /// <summary>
        /// HmacSha1加密 
        /// </summary>
        /// <param name="key">密钥</param>
        /// <param name="text">内容</param>
        /// <returns></returns>
        public byte[] ToHmacSha1(string key, string text)
        {
            if (key != null && text != null)
            {
                HMACSHA1 sha1 = new HMACSHA1(Encoding.UTF8.GetBytes(key));
                return sha1.ComputeHash(Encoding.UTF8.GetBytes(text));
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// 转Base64
        /// </summary>
        /// <param name="text">内容</param>
        /// <returns></returns>
        public string ToBase64(string text)
        {
            if (text != null)
            {
                return ToBase64(Encoding.UTF8.GetBytes(text));
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// 转Base64
        /// </summary>
        /// <param name="bytes">内容字节</param>
        /// <returns></returns>
        public string ToBase64(byte[] bytes)
        {
            if (bytes != null)
            {
                return Convert.ToBase64String(bytes);
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// MD5加密
        /// </summary>
        /// <param name="text">内容</param>
        /// <returns></returns>
        public string MD5(string text)
        {
            if (text != null)
            {
                MD5 md5 = new MD5CryptoServiceProvider();
                byte[] bytes = md5.ComputeHash(UnicodeEncoding.UTF8.GetBytes(text));
                return BitConverter.ToString(bytes).Replace("-", "").ToLower();
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// 又拍云授权
        /// </summary>
        /// <param name="method">提交方式</param>
        /// <param name="uri">请求路径</param>
        /// <param name="headers">头部</param>
        private void UpyunAuth(string method, string uri, Dictionary<string, object> headers)
        {
            string date = DateTime.UtcNow.ToString("ddd, dd MMM yyyy HH':'mm':'ss 'GMT'", CultureInfo.CreateSpecificCulture("en-US"));
            string auth = ToBase64(ToHmacSha1(MD5(this.Password), string.Format("{0}&{1}&{2}", method, uri, date)));

            headers.Add("Date", date);
            headers.Add("Authorization", string.Format("UpYun {0}:{1}", this.UserName, auth));
        }

        /// <summary>
        /// 授权文件浏览
        /// </summary>
        /// <param name="filePath">云空间文件路径</param>
        /// <param name="download">是否下载</param>
        /// <param name="effective">授权有效时间(秒)</param>
        /// <returns></returns>
        public string AuthBrowsingFile(string filePath, bool download = false, string previewDomain = null, int effective = 3600)
        {
            DateTime date = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
            string effectiveSign = ((DateTime.Now.ToUniversalTime().Ticks - date.Ticks) / 10000000 + effective).ToString();
            string tokenSign = MD5(string.Format("{0}&{1}&{2}", this.UserName, effectiveSign, filePath));
            return string.Format("{0}{1}?{2}_upt={3}{4}", previewDomain, filePath, download ? "_upd=true&" : null, tokenSign.Substring(12, 8), effectiveSign);
        }

        /// <summary>
        /// 请求
        /// </summary>
        /// <param name="method">提交方式</param>
        /// <param name="apiDomain">域名</param>
        /// <param name="uri">请求路径</param>
        /// <param name="postData">post参数</param>
        /// <param name="headers">请求头部</param>
        /// <param name="contentMd5">上传文件的 MD5 值，如果请求中文件太大计算 MD5 不方便，可以为空</param>
        /// <param name="contentSecret">文件密钥，用于保护文件，防止文件被直接访问</param>
        /// <param name="upAuth">是否使用又拍云授权</param>
        /// <param name="accept"></param>
        /// <param name="timeout">请求超时时间默认三十秒(单位秒)</param>
        /// <returns></returns>
        private async Task<HttpResponseMessage> Request(string method, string apiDomain, string uri, byte[] postData = null,
            Dictionary<string, object> headers = null, string contentMd5 = null, string contentSecret = null,
            bool upAuth = false, string accept = null, int timeout = 30)
        {
            this.TemporaryInformation.Clear();
            if (headers == null)
            {
                headers = new Dictionary<string, object>();
            }

            using (HttpClientHandler handler = new HttpClientHandler() { AutomaticDecompression = DecompressionMethods.None })
            {
                using (HttpClient httpClient = new HttpClient(handler))
                {
                    httpClient.BaseAddress = new Uri(apiDomain);
                    httpClient.Timeout = new TimeSpan(hours: 0, minutes: 0, seconds: timeout);
                    httpClient.DefaultRequestHeaders.Accept.Clear();
                    //httpClient.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));

                    #region Headers

                    if (contentMd5 != null)
                    {
                        httpClient.DefaultRequestHeaders.Add("Content-MD5", contentMd5);
                    }

                    if (contentSecret != null)
                    {
                        httpClient.DefaultRequestHeaders.Add("Content-Secret", contentSecret);
                    }

                    if (this.AutoMkdir)
                    {
                        httpClient.DefaultRequestHeaders.Add("mkdir", "true");
                    }

                    if (accept != null)
                    {
                        httpClient.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue(accept));
                    }

                    if (upAuth)
                    {
                        UpyunAuth(method, uri, headers);
                    }
                    else
                    {
                        httpClient.DefaultRequestHeaders.Add("Authorization", string.Format("Basic {0}", Convert.ToBase64String(new ASCIIEncoding().GetBytes(string.Format("{0}:{1}", this.UserName, this.Password)))));
                    }

                    foreach (KeyValuePair<string, object> item in headers)
                    {
                        httpClient.DefaultRequestHeaders.Add(item.Key, item.Value.ToString());
                    }

                    #endregion

                    HttpResponseMessage response = null;

                    switch (method?.ToUpper())
                    {
                        case "DELETE":
                            response = await httpClient.DeleteAsync(uri);
                            break;
                        case "HEAD":
                            response = await httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, uri));
                            break;
                        case "OPTIONS":
                            response = await httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Options, uri));
                            break;
                        case "PUT":
                            response = await httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Put, uri));
                            break;
                        case "TRACE":
                            response = await httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Trace, uri));
                            break;
                        case "GET":
                            response = await httpClient.GetAsync(uri);
                            break;
                        case "POST":
                            if (postData != null)
                            {
                                using (StreamContent content = new StreamContent(new MemoryStream(postData)))
                                {
                                    response = await httpClient.PostAsync(uri, content);
                                }
                            }
                            else
                            {
                                response = await httpClient.PostAsync(uri, null);
                            }
                            break;
                    }
                    return response;
                }
            }

        }

        /// <summary>
        /// 格式化Uri
        /// </summary>
        /// <param name="uri"></param>
        /// <returns></returns>
        private static string FormatUri(string uri)
        {
            string result = null;
            if (uri != null)
            {
                result = Regex.Replace(input: uri, pattern: @"(\\|/)+", "/");
            }
            return result;
        }

        /// <summary>
        /// 获取服务用量
        /// </summary>
        /// <returns></returns>
        public long GetUsage()
        {
            long size = 0;
            using (HttpResponseMessage response = Request(method: HttpMethod.Get.Method, apiDomain: ApiDomainStorage, uri: string.Format("{0}{1}/?usage", this.RootDirectory, this.BucketName)).Result)
            {
                if (response?.IsSuccessStatusCode == true)
                {
                    size = Convert.ToInt64(response.Content.ReadAsStringAsync().Result);
                }
            }
            return size;
        }

        /// <summary>
        /// 删除目录、文件夹
        /// </summary>
        /// <param name="path">路径</param>
        /// <returns></returns>
        public bool Delete(string path)
        {
            using (HttpResponseMessage response = Request(method: HttpMethod.Delete.Method, apiDomain: ApiDomainStorage, uri: string.Format("{0}{1}{2}", this.RootDirectory, this.BucketName, path)).Result)
            {
                return response?.IsSuccessStatusCode == true;
            }
        }

        /// <summary>
        /// 创建目录
        /// </summary>
        /// <param name="path">目录路径</param>
        /// <returns></returns>
        public bool CreateDirectory(string path)
        {
            Dictionary<string, object> headers = new Dictionary<string, object>();
            headers.Add("folder", "create");
            using (HttpResponseMessage response = Request(method: HttpMethod.Post.Method, apiDomain: ApiDomainStorage, uri: string.Format("{0}{1}{2}", this.RootDirectory, this.BucketName, path), headers: headers).Result)
            {
                return response?.IsSuccessStatusCode == true;
            }
        }

        /// <summary>
        /// 读取目录
        /// </summary>
        /// <param name="path">路径</param>
        /// <returns></returns>
        public ReadDirectoryResult ReadDirectory(string path)
        {
            ReadDirectoryResult result = null;
            // accept 设置为 application/json 才会返回json结果
            using (HttpResponseMessage response = Request(method: HttpMethod.Get.Method, apiDomain: ApiDomainStorage, uri: string.Format("{0}{1}{2}", this.RootDirectory, this.BucketName, path), accept: "application/json").Result)
            {
                string json = response.Content.ReadAsStringAsync().Result;
                if (!string.IsNullOrWhiteSpace(json))
                {
                    // 又拍云返回对象中files字段在没有数据时是{}
                    // 有数据时是一个数组
                    JObject jsonObject = JsonConvert.DeserializeObject<JObject>(json);
                    if (jsonObject.ContainsKey("files") && jsonObject["files"].HasValues)
                    {
                        result = JsonConvert.DeserializeObject<ReadDirectoryResult>(json);
                        if (result?.Files != null)
                        {
                            path = FormatUri(uri: path);
                            if (!path.EndsWith("/"))
                            {
                                path += "/";
                            }
                            result.Files.ForEach(delegate (FilesModel item)
                            {
                                if (item.FileType == FileTypeEnum.Folder)
                                {
                                    item.Path = string.Format("{0}{1}/", path, item.Name);
                                }
                                else
                                {
                                    item.Path = string.Format("{0}{1}", path, item.Name);
                                }
                            });
                        }
                    }
                }
            }
            return result;
        }

        /// <summary>
        /// 上传文件
        /// </summary>
        /// <param name="fileData">文件流</param>
        /// <param name="savePath">保存路径</param>
        /// <returns></returns>
        public bool UploadFile(byte[] fileData, string savePath)
        {
            using (HttpResponseMessage response = Request(method: HttpMethod.Post.Method, apiDomain: ApiDomainStorage, uri: string.Format("{0}{1}{2}", this.RootDirectory, this.BucketName, savePath), postData: fileData).Result)
            {
                return response?.IsSuccessStatusCode == true;
            }
        }

        /// <summary>
        /// 读取文件
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public byte[] ReadFile(string path)
        {
            byte[] result = null;
            using (HttpResponseMessage response = Request(method: HttpMethod.Get.Method, apiDomain: ApiDomainStorage, uri: string.Format("{0}{1}{2}", this.RootDirectory, this.BucketName, path), accept: "application/json").Result)
            {
                if (response?.IsSuccessStatusCode == true)
                {
                    result = response.Content.ReadAsByteArrayAsync().Result;
                }
            }
            return result;
        }

        /// <summary>
        /// 获取文件信息
        /// </summary>
        /// <param name="path">路径</param>
        /// <returns></returns>
        public FilesModel GetFileInfo(string path)
        {
            FilesModel result = null;
            using (HttpResponseMessage response = Request(method: HttpMethod.Head.Method, apiDomain: ApiDomainStorage, uri: string.Format("{0}{1}{2}", this.RootDirectory, this.BucketName, path), accept: "application/json").Result)
            {
                if (response?.IsSuccessStatusCode == true)
                {
                    result = new FilesModel()
                    {
                        ContentMD5 = Convert.ToBase64String(response.Content.Headers.ContentMD5)
                    };
                    result.Type = response.Headers.GetValues("x-upyun-file-type")?.FirstOrDefault();
                    result.Length = Convert.ToInt64(response.Headers.GetValues("x-upyun-file-size")?.FirstOrDefault());
                    result.last_modified = Convert.ToInt64(response.Headers.GetValues("x-upyun-file-date")?.FirstOrDefault());
                }
            }
            return result;
        }

        /// <summary>
        /// 获取文件MD5值
        /// </summary>
        /// <param name="filePath">文件物理路径</param>
        /// <returns></returns>
        public string MD5File(string filePath)
        {
            string result = null;
            if (!string.IsNullOrWhiteSpace(filePath) && System.IO.File.Exists(filePath))
            {
                MD5CryptoServiceProvider md5Crypto = new MD5CryptoServiceProvider();
                using (FileStream stream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
                {
                    byte[] bytes = md5Crypto.ComputeHash(stream);
                    result = BitConverter.ToString(bytes).Replace("-", null);
                }
            }
            return result;
        }

        /// <summary>
        /// 迁移文件到云空间
        /// </summary>
        /// <param name="fileUrl">需要迁移文件的url</param>
        /// <param name="savePath">云空间保存路径</param>
        /// <param name="notifyUrl">迁移完毕回调地址(必填)</param>
        /// <param name="random">下载是否增加随即数</param>
        /// <param name="overwrite">是否覆盖云空间文件</param>
        /// <returns>任务id</returns>
        public string[] Spiderman(string fileUrl, string savePath, string notifyUrl = null, bool random = true,
            bool overwrite = false)
        {
            string[] result = null;

            #region POST 参数

            string data = string.Format("service={0}&notify_url={1}&tasks={2}&app_name=spiderman",
                this.BucketName,
                HttpUtility.UrlEncode(notifyUrl),
                Convert.ToBase64String(
                    Encoding.UTF8.GetBytes(
                        JsonConvert.SerializeObject(new object[] {
                                        new
                                        {
                                            url = HttpUtility.UrlPathEncode(fileUrl),       // 文件
                                            random = random,                                // 是否追加随机数
                                            overwrite = overwrite,                          // 是否覆盖
                                            save_as = HttpUtility.UrlPathEncode(savePath)   // 保存路径
                                        }
                        }))));
            byte[] postData = Encoding.UTF8.GetBytes(data);

            #endregion

            using (HttpResponseMessage response = Request(method: HttpMethod.Post.Method, apiDomain: ApiDomainSyncAudioVideo, uri: "/pretreatment/", postData: postData, upAuth: true).Result)
            {
                string json = response.Content.ReadAsStringAsync().Result;
                if (!string.IsNullOrWhiteSpace(json))
                {
                    result = JsonConvert.DeserializeObject<string[]>(json);
                }
            }

            return result;
        }

        /// <summary>
        /// 获取视频元信息
        /// </summary>
        /// <param name="filePath">云空间文件path</param>
        /// <returns></returns>
        public VideoMetaInformationModel GetVideoProbe(string filePath)
        {
            VideoMetaInformationModel result = null;
            #region POST 参数

            string data = JsonConvert.SerializeObject(new
            {
                source = filePath
            });
            byte[] postData = Encoding.UTF8.GetBytes(data);

            #endregion

            using (HttpResponseMessage response = Request(method: HttpMethod.Post.Method, apiDomain: ApiDomainAudioVideo, uri: string.Format("/{0}/avmeta/get_meta", this.BucketName), postData: postData, upAuth: true).Result)
            {
                string json = response.Content.ReadAsStringAsync().Result;
                if (!string.IsNullOrWhiteSpace(json))
                {
                    result = JsonConvert.DeserializeObject<VideoMetaInformationModel>(json);
                }
            }

            return result;
        }

        /// <summary>
        /// 视频转码
        /// </summary>
        /// <param name="source">云空间文件</param>
        /// <param name="savePath">保存路径</param>
        /// <param name="parameters">转码参数</param>
        /// <param name="notifyUrl">任务回调地址</param>
        /// <returns>视频转码任务id</returns>
        public string[] VideoTranscoding(string source, string savePath, Dictionary<string, object> parameters = null, string notifyUrl = null)
        {
            string[] result = null;

            #region POST 参数

            string avopts = null;
            if (parameters != null)
            {
                avopts = string.Join(string.Empty, parameters.Select(x => string.Format("/{0}/{1}", x.Key, x.Value)));
            }
            string data = string.Format("service={0}&notify_url={1}&source={2}&tasks={3}&accept=json",
                this.BucketName,
                HttpUtility.UrlEncode(notifyUrl),
                HttpUtility.UrlPathEncode(source),
                Convert.ToBase64String(
                    Encoding.UTF8.GetBytes(
                        JsonConvert.SerializeObject(new object[] {
                                        new
                                        {
                                            type = "video",                                 // 视频转码
                                            avopts = avopts,                                // 转码参数
                                            return_info = true,                             // 返回元数据
                                            save_as = HttpUtility.UrlPathEncode(savePath)   // 保存路径
                                        }
                        }))));
            byte[] postData = Encoding.UTF8.GetBytes(data);

            #endregion

            using (HttpResponseMessage response = Request(method: HttpMethod.Post.Method, apiDomain: ApiDomainSyncAudioVideo, uri: "/pretreatment/", postData: postData, upAuth: true).Result)
            {
                string json = response.Content.ReadAsStringAsync().Result;
                if (!string.IsNullOrWhiteSpace(json))
                {
                    result = JsonConvert.DeserializeObject<string[]>(json);
                }
            }

            return result;
        }

        /// <summary>
        /// 视频转码进度查询
        /// </summary>
        /// <param name="taskIds">任务id，多个 task_id 使用 , 连接，最多 20 个 task_id</param>
        public VideoTranscodingStatusModel VideoTranscodingStatus(params string[] taskIds)
        {
            VideoTranscodingStatusModel result = null;
            #region POST 参数

            string data = string.Format("service={0}&task_ids={1}",
                this.BucketName,
                string.Join(",", taskIds));

            #endregion

            using (HttpResponseMessage response = Request(method: HttpMethod.Get.Method, apiDomain: ApiDomainSyncAudioVideo, uri: string.Format("/status/?{0}", data), upAuth: true).Result)
            {
                string json = response.Content.ReadAsStringAsync().Result;
                if (!string.IsNullOrWhiteSpace(json))
                {
                    result = JsonConvert.DeserializeObject<VideoTranscodingStatusModel>(json);
                }
            }
            return result;
        }

        /// <summary>
        /// 查询结果
        /// </summary>
        /// <param name="taskIds">任务id，多个 task_id 使用 , 连接，最多 20 个 task_id</param>
        public VideoTranscodingResultModel VideoTranscodingResult(params string[] taskIds)
        {
            VideoTranscodingResultModel result = null;
            #region POST 参数

            string data = string.Format("service={0}&task_ids={1}",
                this.BucketName,
                string.Join(",", taskIds));

            #endregion

            using (HttpResponseMessage response = Request(method: HttpMethod.Get.Method, apiDomain: ApiDomainSyncAudioVideo, uri: string.Format("/result/?{0}", data), upAuth: true).Result)
            {
                string json = response.Content.ReadAsStringAsync().Result;
                if (!string.IsNullOrWhiteSpace(json))
                {
                    result = JsonConvert.DeserializeObject<VideoTranscodingResultModel>(json);
                }
            }
            return result;
        }
    }
}
